package Servidor;

import Radar.Detectado;
import Baterias.Bateria;
import Baterias.Monitor;
import Misil.*;
import Utilidades.CalculadoraDeMisiles;
import Utilidades.Logger;

import java.util.HashMap;
import java.util.HashSet;



/**
 * @brief Clase Servidor.
 *
 * @version 1.2
 * @created 17-jul-2012 09:09:00 p.m.
 */
public class ServidorEnTierra {
	//Campos de la clase ServidorEnTierra
	private Rx conexion;
	private HashMap<MisilAliado,MisilEnemigo> mapeoMisiles;
	private HashSet<Detectado> enemigosDetectados;
	private Monitor monitor;
	private CalculadoraDeMisiles calculadoraMisiles;
	private Bateria[] baterias = new Bateria[4];
	
	
	/**
	 * @brief Constructor de la clase ServidorEnTierra. 
	 * 		  Crea el monitor y establece la conexion por sockets con el radar.
	 * @param baterias que lanzan los misiles aliados.
	 */
	public ServidorEnTierra(Bateria baterias[],int puerto){
		this.baterias=baterias;
		monitor = new Monitor();
		// Doy a las baterias una referencia al monitor para poder ejecutar los resume
		for (int i = 0; i < 4; i++)
			baterias[i].setMonitor(monitor);
		
		calculadoraMisiles = new CalculadoraDeMisiles();
		mapeoMisiles = new HashMap<MisilAliado,MisilEnemigo>();
		conexion = new Rx(puerto);
	}



	/**		 
	 * @brief Funcion que devuelve el mapa con el mapeo de los aliados y enemigos
	 * @return 
	 */
	public HashMap<MisilAliado,MisilEnemigo> getMapeo()
	{
		return mapeoMisiles;
	}
	


	/**		  
	 * @brief Funcion que toma los datos de los misiles que el radar ha detectado
	 * 		  dos veces y con ellos calcula la velocidad y la trayectoria de los mismos.
	 */
	public void procesarDatosRadar(){
		//Recibir lista de enemigos a travez del socket
		enemigosDetectados=(HashSet<Detectado>)conexion.recibirObjeto();
		for(Detectado objeto: enemigosDetectados){
			double[] puntoDeEntrada = calculadoraMisiles.puntoDeIngresoAZonaProtegida(
					objeto.getPosicionTiempo1(),objeto.getPosicionTiempo2());
			
			//Si se devolvio la ubicacion de "error", Ignorar este misil
			if(puntoDeEntrada[0]==50000&&puntoDeEntrada[1]==50000&&puntoDeEntrada[2]==50000){
				System.out.println("Este misil ("+objeto.getMisil().getID()+")no pega en el area protegida");
				Logger.escribir("Este misil ("+objeto.getMisil().getID()+")no pega en el area protegida");
			}
			else{
				//Imprimir por donde ingresa el enemigo
				printIngresoEnemigo(objeto,puntoDeEntrada);

				/* Ahora se elige una bateria segun el punto por donde ingresa el enemigo, pero
				 * si dicha bateria no esta disponible, se intenta elegir otra. Si no hay otra
				 * disponible, se continua con la eleccion inicial.
				 * */
				int numBateria = elegirBateria(puntoDeEntrada);
				
				//Tras Elegir la bateria que corresponde, lanzar un misil aliado con esa bateria.
				MisilAliado aliado = generarMisilAliado(puntoDeEntrada,numBateria);
				//Le aviso alenemigo que lo tengo junado
				MisilEnemigo enemigo = (MisilEnemigo) objeto.getMisil();
				enemigo.setGonnaDieTrue();
				//Metes los misiles en el mapeo
				mapeoMisiles.put(aliado, enemigo);
			}
		}
	}
	
	
	/**
	 * @brief Metodo que elige una bateria segun el punto por donde ingresa el enemigo, pero
	 *		  si dicha bateria no esta disponible, se intenta elegir otra. Si no hay otra
	 *		  disponible, se continua con la eleccion inicial.		 
	 * @param puntoDeEntrada
	 * @return
	 */
	private int elegirBateria(double[] puntoDeEntrada){
		int numBateria = seleccionarBateria(puntoDeEntrada);
		boolean[] disponibles = monitor.getDisponibilidad();
		if(!disponibles[numBateria])
			numBateria=elegirOtraBateria(numBateria,disponibles);
		return numBateria;
	}
	
	
	
	/**
	 * @brief Funcion que crea un nuevo misil aliado para interceptar a un enemigo. Se crea un thread
	 * 		  para dicho misil aliado y se lo pone a correr.
	 * @param objetivo es el punto de entrada a la zona protegida por parte del enemigo
	 */
	private  MisilAliado generarMisilAliado(double[] objetivo,int numBateria){
		MisilAliado aliado = new MisilAliado(monitor,this,baterias[numBateria]);
		aliado.getComportamiento().setObjetivo(objetivo);
		Thread hilo = new Thread(aliado);
		hilo.setName("MisilAliado "+aliado.getID());
		hilo.start();
		return aliado;
	}

	
	
	/**	
	 * @brief Funcion que elige una bateria en funcion de su distancia al misil enemigo que se
	 * 		  desea eliminar.
	 * @param ubicacionEnemigo es el punto de entrada del enemigo ala zona protegida.
	 * @return Indice de la bateria elegida
	 */
	private int seleccionarBateria(double[] ubicacionEnemigo){
		
		int indiceElegido=0;
		double menorDistancia=0;
		for(int i=0;i<4;i++){
			double[] ubicacion = baterias[i].getUbicacion();
			double distancia[] = new double[3];
			distancia[0]=ubicacion[0]-ubicacionEnemigo[0];
			distancia[1]=ubicacion[1]-ubicacionEnemigo[1];
			distancia[2]=ubicacion[2]-ubicacionEnemigo[2];
			
			double distanciaActual=Math.sqrt(Math.pow(distancia[0],2)+Math.pow(distancia[1],2)+Math.pow(distancia[2],2));
			
			if((menorDistancia==0)||(menorDistancia>distanciaActual)){
				menorDistancia=distanciaActual;
				indiceElegido=i;
			}
		}	
		return indiceElegido;
	}
	
	
	
	/**
	 * @brief Funcion que muestra el punto de entrada correspondiente a un enemigo
	 * 		  y lo registra en el Log
	 * @param objeto
	 * @param puntoDeEntrada
	 */
	private void printIngresoEnemigo(Detectado objeto,double[] puntoDeEntrada){
		double R=Math.sqrt(puntoDeEntrada[0]*puntoDeEntrada[0]+puntoDeEntrada[1]*puntoDeEntrada[1]);
		System.out.println("El Enemigo ("+objeto.getMisil().getID()+") ingresa por el punto: ("+
				puntoDeEntrada[0]+";"+puntoDeEntrada[1]+";"+puntoDeEntrada[2]+")  R="+R);
		Logger.escribir("El Enemigo ("+objeto.getMisil().getID()+") ingresa por el punto: ("+
				puntoDeEntrada[0]+";"+puntoDeEntrada[1]+";"+puntoDeEntrada[2]+")");	
	}
	
	
	
	/**
	 * @brief Metodo que busca una bateria alternativa si es que hay alguna disponible
	 * 		  En caso de que no haya, elige la misma cuyo indice se le paso como parametro.
	 * 		  Se elige la primera bateria libre que se encuentra.
	 * @param indice
	 * @return
	 */
	private int elegirOtraBateria(int indice,boolean[] disponibles){
		//boolean[]verdaderos=monitor.getDisponibilidad();
		for(int i=0;i<4;i++)
			if(disponibles[i]){
				return i;
			}

		return indice;
	}
	
}